/*
 * This file is part of aion-engine <aion-engine.net>
 *
 * aion-engine is private software: you can redistribute it and or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Private Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aion-engine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser Public License
 * along with aion-engine.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.aionengine.gameserver.services.player;

import com.aionemu.commons.database.dao.DAOManager;
import com.aionemu.commons.versionning.Version;
import com.aionengine.gameserver.GameServer;
import com.aionengine.gameserver.cache.HTMLCache;
import com.aionengine.gameserver.configs.administration.AdminConfig;
import com.aionengine.gameserver.configs.main.*;
import com.aionengine.gameserver.dao.*;
import com.aionengine.gameserver.dataholders.DataManager;
import com.aionengine.gameserver.model.ChatType;
import com.aionengine.gameserver.model.TaskId;
import com.aionengine.gameserver.model.account.Account;
import com.aionengine.gameserver.model.account.AccountMembershipType;
import com.aionengine.gameserver.model.account.CharacterBanInfo;
import com.aionengine.gameserver.model.account.CharacterPasskey.ConnectType;
import com.aionengine.gameserver.model.account.PlayerAccountData;
import com.aionengine.gameserver.model.gameobjects.HouseObject;
import com.aionengine.gameserver.model.gameobjects.Item;
import com.aionengine.gameserver.model.gameobjects.PersistentState;
import com.aionengine.gameserver.model.gameobjects.player.Player;
import com.aionengine.gameserver.model.gameobjects.player.PlayerCommonData;
import com.aionengine.gameserver.model.gameobjects.player.emotion.Emotion;
import com.aionengine.gameserver.model.gameobjects.player.motion.Motion;
import com.aionengine.gameserver.model.gameobjects.player.title.Title;
import com.aionengine.gameserver.model.gameobjects.state.CreatureSeeState;
import com.aionengine.gameserver.model.gameobjects.state.CreatureVisualState;
import com.aionengine.gameserver.model.house.House;
import com.aionengine.gameserver.model.items.storage.IStorage;
import com.aionengine.gameserver.model.items.storage.Storage;
import com.aionengine.gameserver.model.items.storage.StorageType;
import com.aionengine.gameserver.model.skill.PlayerSkillEntry;
import com.aionengine.gameserver.model.team2.alliance.PlayerAllianceService;
import com.aionengine.gameserver.model.team2.group.PlayerGroupService;
import com.aionengine.gameserver.model.templates.QuestTemplate;
import com.aionengine.gameserver.model.templates.quest.QuestExtraCategory;
import com.aionengine.gameserver.network.aion.AionConnection;
import com.aionengine.gameserver.network.aion.serverpackets.*;
import com.aionengine.gameserver.questEngine.model.QuestState;
import com.aionengine.gameserver.questEngine.model.QuestStatus;
import com.aionengine.gameserver.services.*;
import com.aionengine.gameserver.services.PunishmentService.PunishmentType;
import com.aionengine.gameserver.services.abyss.AbyssSkillService;
import com.aionengine.gameserver.services.craft.RelinquishCraftStatus;
import com.aionengine.gameserver.services.instance.InstanceService;
import com.aionengine.gameserver.services.json.JsonService;
import com.aionengine.gameserver.services.mail.MailService;
import com.aionengine.gameserver.services.teleport.TeleportService2;
import com.aionengine.gameserver.services.toypet.PetService;
import com.aionengine.gameserver.services.transfers.PlayerTransferService;
import com.aionengine.gameserver.skillengine.effect.AbnormalState;
import com.aionengine.gameserver.taskmanager.tasks.ExpireTimerTask;
import com.aionengine.gameserver.utils.PacketSendUtility;
import com.aionengine.gameserver.utils.ThreadPoolManager;
import com.aionengine.gameserver.utils.audit.AuditLogger;
import com.aionengine.gameserver.utils.audit.GMService;
import com.aionengine.gameserver.utils.collections.ListSplitter;
import com.aionengine.gameserver.utils.rates.ap.ApRates;
import com.aionengine.gameserver.utils.rates.collection.CollectionRates;
import com.aionengine.gameserver.utils.rates.craft.CraftRates;
import com.aionengine.gameserver.utils.rates.regular.Rates;
import com.aionengine.gameserver.world.World;
import com.aionengine.gameserver.world.knownlist.Visitor;

import javolution.util.FastList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Timestamp;
import java.util.*;

/**
 * @author ATracer
 * @modify xXMashUpXx
 */
public final class PlayerEnterWorldService {

    private static final Logger log = LoggerFactory.getLogger("GAMECONNECTION_LOG");
    private static final String serverAnnounce;
    private static final String alInfo;
    private static final String serverInfo;
    private static final Set<Integer> pendingEnterWorld = new HashSet<>();

    static {
        String announceBuffer;
        String alBuffer;
        String emuBuffer;
        @SuppressWarnings("unused")
		Calendar cal = Calendar.getInstance();

        announceBuffer = MuiService.getInstance().getMessage("HELLO_STAFF", GSConfig.SERVER_NAME) + "\n";
        announceBuffer += MuiService.getInstance().getMessage("HELLO_RULLES") + "\n";
        if (GSConfig.SERVER_ANNOUNCE_INFO) {
            announceBuffer += MuiService.getInstance().getMessage("HELLO_ANNOUNCE") + "\n";
            announceBuffer += MuiService.getInstance().getMessage("HELLO_ANNOUNCE_INFO1") + "\n";
            announceBuffer += MuiService.getInstance().getMessage("HELLO_ANNOUNCE_INFO2") + "\n";
            announceBuffer += MuiService.getInstance().getMessage("HELLO_ANNOUNCE_INFO3") + "\n";
        }
        announceBuffer += "=============================";


        alBuffer = MuiService.getInstance().getMessage("HELLO_ENJOY", GSConfig.SERVER_NAME);
        alBuffer += " ";

        emuBuffer = "---------------------------------------\n";
        emuBuffer += "Copyright " + GSConfig.SERVER_NAME + " 2014 Derechos reservados.\n";
        if (GSConfig.SERVER_MOTD_DISPLAYREV) {
            emuBuffer += "Revision: " + String.format("%-6s", new Version(GameServer.class).getRevision()) + "\n";
        }

        // gm info

        serverAnnounce = announceBuffer;
        alInfo = alBuffer;
        serverInfo = emuBuffer;

        announceBuffer = null;
        alBuffer = null;
        emuBuffer = null;
    }

    /**
     * @param objectId
     * @param client
     */
    public static final void startEnterWorld(final int objectId, final AionConnection client) {
        // check if char is banned
        PlayerAccountData playerAccData = client.getAccount().getPlayerAccountData(objectId);
        if (playerAccData == null) {
            log.warn("playerAccData == null " + objectId);
            return;
        }
        if (playerAccData.getPlayerCommonData() == null) {
            log.warn("playerAccData.getPlayerCommonData() == null " + objectId);
            return;
        }
        Timestamp lastOnline = playerAccData.getPlayerCommonData().getLastOnline();
        if (lastOnline != null && client.getAccount().getAccessLevel() < AdminConfig.GM_LEVEL) {
            if (System.currentTimeMillis() - lastOnline.getTime() < (GSConfig.CHARACTER_REENTRY_TIME * 1000)) {
                client.sendPacket(new SM_ENTER_WORLD_CHECK((byte) 6)); // 20 sec time
                return;
            }
        }
        CharacterBanInfo cbi = client.getAccount().getPlayerAccountData(objectId).getCharBanInfo();
        if (cbi != null) {
            if (cbi.getEnd() > System.currentTimeMillis() / 1000) {
                client.close(new SM_QUIT_RESPONSE(), false);
                return;
            } else {
                DAOManager.getDAO(PlayerPunishmentsDAO.class).unpunishPlayer(objectId, PunishmentType.CHARBAN);
            }
        }
        // passkey check
        if (SecurityConfig.PASSKEY_ENABLE && !client.getAccount().getCharacterPasskey().isPass()) {
            showPasskey(objectId, client);
        } else {
            validateAndEnterWorld(objectId, client);
        }
    }

    /**
     * @param objectId
     * @param client
     */
    private static final void showPasskey(final int objectId, final AionConnection client) {
        client.getAccount().getCharacterPasskey().setConnectType(ConnectType.ENTER);
        client.getAccount().getCharacterPasskey().setObjectId(objectId);
        boolean isExistPasskey = DAOManager.getDAO(PlayerPasskeyDAO.class).existCheckPlayerPasskey(client.getAccount().getId());

        if (!isExistPasskey)
            client.sendPacket(new SM_CHARACTER_SELECT(0));
        else
            client.sendPacket(new SM_CHARACTER_SELECT(1));
    }

    /**
     * @param objectId
     * @param client
     */
    private static final void validateAndEnterWorld(final int objectId, final AionConnection client) {
        synchronized (pendingEnterWorld) {
            if (pendingEnterWorld.contains(objectId)) {
                log.warn("Skipping enter world " + objectId);
                return;
            }
            pendingEnterWorld.add(objectId);
        }
        int delay = 0;
        // double checked enter world
        if (World.getInstance().findPlayer(objectId) != null) {
            delay = 15000;
            log.warn("Postponed enter world " + objectId);
        }
        ThreadPoolManager.getInstance().schedule(new Runnable() {
            @Override
            public void run() {
                try {
                    Player player = World.getInstance().findPlayer(objectId);
                    if (player != null) {
                        AuditLogger.info(player, "Duplicate player in world");
                        client.close(new SM_QUIT_RESPONSE(), false);
                        return;
                    }
                    enterWorld(client, objectId);
                } catch (Throwable ex) {
                    log.error("Error during enter world " + objectId, ex);
                } finally {
                    synchronized (pendingEnterWorld) {
                        pendingEnterWorld.remove(objectId);
                    }
                }
            }

        }, delay);
    }

    /**
     * @param client
     * @param objectId
     */
    public static final void enterWorld(AionConnection client, int objectId) {
        Account account = client.getAccount();
        PlayerAccountData playerAccData = client.getAccount().getPlayerAccountData(objectId);

        if (playerAccData == null) {
            // Somebody wanted to login on character that is not at his account
            return;
        }
        Player player = PlayerService.getPlayer(objectId, account);

        if (player != null && client.setActivePlayer(player)) {
            player.setClientConnection(client);

            log.info("[MAC_AUDIT] Player " + player.getName() + " (account " + account.getName() + ") has entered world with "
                    + client.getMacAddress() + " MAC.");
            World.getInstance().storeObject(player);

            StigmaService.onPlayerLogin(player);

            /**
             * Energy of Repose must be calculated before sending SM_STATS_INFO
             */
            if (playerAccData.getPlayerCommonData().getLastOnline() != null) {
                long lastOnline = playerAccData.getPlayerCommonData().getLastOnline().getTime();
                PlayerCommonData pcd = player.getCommonData();
                long secondsOffline = (System.currentTimeMillis() / 1000) - lastOnline / 1000;
                if (pcd.isReadyForSalvationPoints()) {
                    // 10 mins offline = 0 salvation points.
                    if (secondsOffline > 10 * 60) {
                        player.getCommonData().resetSalvationPoints();
                    }
                }
                if (pcd.isReadyForReposteEnergy()) {
                    pcd.updateMaxReposte();
                    // more than 4 hours offline = start counting Reposte Energy addition.
                    if (secondsOffline > 4 * 3600) {
                        double hours = secondsOffline / 3600d;
                        long maxRespose = player.getCommonData().getMaxReposteEnergy();
                        if (hours > 24)
                            hours = 24;
                        // 24 hours offline = 100% Reposte Energy
                        long addResposeEnergy = (long) ((hours / 24) * maxRespose);

                        // Additional Energy of Repose bonus
                        // TODO: use player house zones
                        if (player.getHouseOwnerId() / 10000 * 10000 == player.getWorldId()) {
                            switch (player.getActiveHouse().getHouseType()) {
                            	case STUDIO:
                                    addResposeEnergy *= 1.05f;
                                    break;
                                case MANSION:
                                    addResposeEnergy *= 1.08f;
                                    break;
                                case ESTATE:
                                    addResposeEnergy *= 1.15f;
                                    break;
                                case PALACE:
                                    addResposeEnergy *= 1.50f;
                                    break;
                                default:
                                    addResposeEnergy *= 1.10f;
                                    break;
                            }
                        }

                        pcd.addReposteEnergy(addResposeEnergy > maxRespose ? maxRespose : addResposeEnergy);
                    }
                }
                if (((System.currentTimeMillis() / 1000) - lastOnline) > 300)
                    player.getCommonData().setDp(0);
            }
            InstanceService.onPlayerLogin(player);
            client.sendPacket(new SM_UNK_3_5_1());
            if (!player.getSkillList().isSkillPresent(3512)) {
                player.getSkillList().addSkill(player, 3512, 129);
            }
            // Update player skills first!!!
            AbyssSkillService.onEnterWorld(player);
            // TODO: check the split size
            client.sendPacket(new SM_SKILL_LIST(player, player.getSkillList().getBasicSkills()));
            for (PlayerSkillEntry stigmaSkill : player.getSkillList().getStigmaSkills())
                client.sendPacket(new SM_SKILL_LIST(player, stigmaSkill));

            if (player.getSkillCoolDowns() != null)
                client.sendPacket(new SM_SKILL_COOLDOWN(player.getSkillCoolDowns()));

            if (player.getItemCoolDowns() != null)
                client.sendPacket(new SM_ITEM_COOLDOWN(player.getItemCoolDowns()));

            FastList<QuestState> questList = FastList.newInstance();
            FastList<QuestState> completeQuestList = FastList.newInstance();
            for (QuestState qs : player.getQuestStateList().getAllQuestState()) {
                QuestTemplate questTemplate = DataManager.QUEST_DATA.getQuestById(qs.getQuestId());
                if (questTemplate.getExtraCategory() != QuestExtraCategory.NONE)
                    continue;
                if (qs.getStatus() == QuestStatus.NONE && qs.getCompleteCount() == 0)
                    continue;
                if (qs.getStatus() != QuestStatus.COMPLETE && qs.getStatus() != QuestStatus.NONE)
                    questList.add(qs);
                if (qs.getCompleteCount() > 0)
                    completeQuestList.add(qs);
            }
            client.sendPacket(new SM_QUEST_COMPLETED_LIST(completeQuestList));
            client.sendPacket(new SM_QUEST_LIST(questList));
            client.sendPacket(new SM_TITLE_INFO(player.getCommonData().getTitleId()));
            client.sendPacket(new SM_TITLE_INFO(6, player.getCommonData().getBonusTitleId()));
            client.sendPacket(new SM_MOTION(player.getMotions().getMotions().values()));
            client.sendPacket(new SM_ENTER_WORLD_CHECK());

            byte[] uiSettings = player.getPlayerSettings().getUiSettings();
            byte[] shortcuts = player.getPlayerSettings().getShortcuts();
            byte[] houseBuddies = player.getPlayerSettings().getHouseBuddies();

            if (uiSettings != null)
                client.sendPacket(new SM_UI_SETTINGS(uiSettings, 0));

            if (shortcuts != null)
                client.sendPacket(new SM_UI_SETTINGS(shortcuts, 1));

            if (houseBuddies != null)
                client.sendPacket(new SM_UI_SETTINGS(houseBuddies, 2));

            sendItemInfos(client, player);
            playerLoggedIn(player);

            client.sendPacket(new SM_INSTANCE_INFO(player, false, player.getCurrentTeam()));

            client.sendPacket(new SM_CHANNEL_INFO(player.getPosition()));

            KiskService.getInstance().onLogin(player);
            TeleportService2.sendSetBindPoint(player);

            // Without player spawn initialization can't access to his mapRegion for chk below
            World.getInstance().preSpawn(player);
            player.getController().validateLoginZone();
            VortexService.getInstance().validateLoginZone(player);

            client.sendPacket(new SM_PLAYER_SPAWN(player));

            // SM_WEATHER miss on login (but he 'live' in CM_LEVEL_READY.. need invistigate)
            client.sendPacket(new SM_GAME_TIME());

            SerialKillerService.getInstance().onLogin(player);

            if (player.isLegionMember())
                LegionService.getInstance().onLogin(player);

            client.sendPacket(new SM_TITLE_INFO(player));
            client.sendPacket(new SM_EMOTION_LIST((byte) 0, player.getEmotions().getEmotions()));

            // SM_INFLUENCE_RATIO, SM_SIEGE_LOCATION_INFO, SM_RIFT_ANNOUNCE (Balaurea), SM_RIFT_ANNOUNCE (Tiamaranta)
            SiegeService.getInstance().onPlayerLogin(player);

            client.sendPacket(new SM_PRICES());
            DisputeLandService.getInstance().onLogin(player);
            client.sendPacket(new SM_ABYSS_RANK(player.getAbyssRank()));

            // Intro message
            PacketSendUtility.sendMessage(player, "---------------------------------------");
            PacketSendUtility.sendWhiteMessage(player, MuiService.getInstance().getMessage("HELLO_WORLD", GSConfig.SERVER_WORLDNAME));
            PacketSendUtility.sendYellowMessage(player, MuiService.getInstance().getMessage("HELLO_REMEMBER"));
            PacketSendUtility.sendBrightYellowMessage(player, serverAnnounce);

            player.setRates(Rates.getRatesFor(client.getAccount().getAccountMembership().getMembership()));
            player.setCraftRates(CraftRates.getRatesFor(client.getAccount().getAccountMembership().getCraftship()));
            player.setApRates(ApRates.getRatesFor(client.getAccount().getAccountMembership().getApship()));
            player.setCollectionRates(CollectionRates.getRatesFor(client.getAccount().getAccountMembership().getCollectionship()));

            if (CustomConfig.DISPLAY_RATE) {
                PacketSendUtility.sendYellowMessage(player, MuiService.getInstance().getMessage("ANNOUNCE_RATES"));
                PacketSendUtility.sendYellowMessage(player, MuiService.getInstance().getMessage("ANNOUNCE_RATES_XP", player.getRates().getXpRate()));
                PacketSendUtility.sendYellowMessage(player, MuiService.getInstance().getMessage("ANNOUNCE_RATES_QS", player.getRates().getQuestXpRate()));
                PacketSendUtility.sendYellowMessage(player, MuiService.getInstance().getMessage("ANNOUNCE_RATES_DR", player.getRates().getDropRate()) + "\n");
            }
            if (CustomConfig.PREMIUM_NOTIFY) {
                showPremiumAccountInfo(client, account);
            }

            PacketSendUtility.sendBrightYellowMessage(player, alInfo);
            PacketSendUtility.sendWhiteMessage(player, serverInfo);

            if (player.isGM()) {
                if (AdminConfig.INVULNERABLE_GM_CONNECTION || AdminConfig.INVISIBLE_GM_CONNECTION
                        || AdminConfig.ENEMITY_MODE_GM_CONNECTION.equalsIgnoreCase("Neutral")
                        || AdminConfig.ENEMITY_MODE_GM_CONNECTION.equalsIgnoreCase("Enemy") || AdminConfig.VISION_GM_CONNECTION
                        || AdminConfig.WHISPER_GM_CONNECTION) {
                    //PacketSendUtility.sendMessage(player, "=============================");
                    if (AdminConfig.INVULNERABLE_GM_CONNECTION) {
                        player.setInvul(true);
                        PacketSendUtility.sendMessage(player, MuiService.getInstance().getMessage("GM_MODE_INVULNERABLE"));
                    }
                    if (AdminConfig.INVISIBLE_GM_CONNECTION) {
                        player.getEffectController().setAbnormal(AbnormalState.HIDE.getId());
                        player.setVisualState(CreatureVisualState.HIDE20);
                        PacketSendUtility.broadcastPacket(player, new SM_PLAYER_STATE(player), true);
                        PacketSendUtility.sendMessage(player, MuiService.getInstance().getMessage("GM_MODE_INVISIBLE"));
                    }
                    if (AdminConfig.ENEMITY_MODE_GM_CONNECTION.equalsIgnoreCase("Neutral")) {
                        player.setAdminNeutral(3);
                        player.setAdminEnmity(0);
                        PacketSendUtility.sendMessage(player, MuiService.getInstance().getMessage("GM_MODE_NEUTRAL"));
                    }
                    if (AdminConfig.ENEMITY_MODE_GM_CONNECTION.equalsIgnoreCase("Enemy")) {
                        player.setAdminNeutral(0);
                        player.setAdminEnmity(3);
                        PacketSendUtility.sendMessage(player, MuiService.getInstance().getMessage("GM_MODE_ENEMY"));
                    }
                    if (AdminConfig.VISION_GM_CONNECTION) {
                        player.setSeeState(CreatureSeeState.SEARCH10);
                        PacketSendUtility.broadcastPacket(player, new SM_PLAYER_STATE(player), true);
                        PacketSendUtility.sendMessage(player, MuiService.getInstance().getMessage("GM_MODE_VISION"));
                    }
                    if (AdminConfig.WHISPER_GM_CONNECTION) {
                        player.setUnWispable();
                        PacketSendUtility.sendMessage(player, MuiService.getInstance().getMessage("GM_MODE_WHISPER"));
                    }
                    PacketSendUtility.sendMessage(player, "=============================");
                }
                // Special skill for gm
                if (player.getAccessLevel() >= AdminConfig.COMMAND_SPECIAL_SKILL) {
                    FastList<Integer> gmSkill = FastList.newInstance();
                    gmSkill.add(174);
                    gmSkill.add(175);
                    gmSkill.add(1904);
                    gmSkill.add(1911);
                    gmSkill.add(3224);
                    gmSkill.add(3226);
                    gmSkill.add(3227);
                    gmSkill.add(3232);
                    gmSkill.add(3233);
                    gmSkill.add(3234);
                    gmSkill.add(3235);
                    gmSkill.add(3236);
                    gmSkill.add(3237);
                    gmSkill.add(3238);
                    gmSkill.add(3239);
                    gmSkill.add(3240);
                    gmSkill.add(3241);
                    for (FastList.Node<Integer> n = gmSkill.head(), end = gmSkill.tail(); (n = n.getNext()) != end; ) {
                        PlayerSkillEntry skill = new PlayerSkillEntry(n.getValue(), true, 1, PersistentState.NOACTION);
                        player.getSkillList().addStigmaSkill(player, skill.getSkillId(), skill.getSkillLevel());
                    }
                }
            }

            // Alliance Packet after SetBindPoint
            PlayerAllianceService.onPlayerLogin(player);

            if (player.isInPrison())
                PunishmentService.updatePrisonStatus(player);

            if (player.isNotGatherable())
                PunishmentService.updateGatherableStatus(player);

            PlayerGroupService.onPlayerLogin(player);
            PetService.getInstance().onPlayerLogin(player);

            // ----------------------------- Retail sequence -----------------------------
            MailService.getInstance().onPlayerLogin(player);
            HousingService.getInstance().onPlayerLogin(player);
            BrokerService.getInstance().onPlayerLogin(player);
            sendMacroList(client, player);
            client.sendPacket(new SM_RECIPE_LIST(player.getRecipeList().getRecipeList()));
            // ----------------------------- Retail sequence -----------------------------

            PetitionService.getInstance().onPlayerLogin(player);
            if (AutoGroupConfig.AUTO_GROUP_ENABLE) {
                AutoGroupService.getInstance().onPlayerLogin(player);
            }
            ClassChangeService.showClassChangeDialog(player);

            GMService.getInstance().onPlayerLogin(player);
            /**
             * Trigger restore services on login.
             */
            player.getLifeStats().updateCurrentStats();

            if (HTMLConfig.ENABLE_HTML_WELCOME)
                HTMLService.showHTML(player, HTMLCache.getInstance().getHTML("welcome.xhtml"));

            JsonService.getInstance().onPlayerLogin(player);

            player.getNpcFactions().sendDailyQuest();

            if (HTMLConfig.ENABLE_GUIDES)
                HTMLService.onPlayerLogin(player);

            for (StorageType st : StorageType.values()) {
                if (st == StorageType.LEGION_WAREHOUSE)
                    continue;
                IStorage storage = player.getStorage(st.getId());
                if (storage != null) {
                    for (Item item : storage.getItemsWithKinah())
                        if (item.getExpireTime() > 0)
                            ExpireTimerTask.getInstance().addTask(item, player);
                }
            }

            for (Item item : player.getEquipment().getEquippedItems())
                if (item.getExpireTime() > 0)
                    ExpireTimerTask.getInstance().addTask(item, player);

            player.getEquipment().checkRankLimitItems(); // Remove items after offline changed rank

            for (Motion motion : player.getMotions().getMotions().values()) {
                if (motion.getExpireTime() != 0) {
                    ExpireTimerTask.getInstance().addTask(motion, player);
                }
            }

            for (Emotion emotion : player.getEmotions().getEmotions()) {
                if (emotion.getExpireTime() != 0) {
                    ExpireTimerTask.getInstance().addTask(emotion, player);
                }
            }

            for (Title title : player.getTitleList().getTitles()) {
                if (title.getExpireTime() != 0) {
                    ExpireTimerTask.getInstance().addTask(title, player);
                }
            }

            if (player.getHouseRegistry() != null) {
                for (HouseObject<?> obj : player.getHouseRegistry().getObjects()) {
                    if (obj.getPersistentState() == PersistentState.DELETED)
                        continue;
                    if (obj.getObjectTemplate().getUseDays() > 0)
                        ExpireTimerTask.getInstance().addTask(obj, player);
                }
            }
            // scheduler periodic update
            player.getController().addTask(
                    TaskId.PLAYER_UPDATE,
                    ThreadPoolManager.getInstance().scheduleAtFixedRate(new GeneralUpdateTask(player.getObjectId()),
                            PeriodicSaveConfig.PLAYER_GENERAL * 1000, PeriodicSaveConfig.PLAYER_GENERAL * 1000));
            player.getController().addTask(
                    TaskId.INVENTORY_UPDATE,
                    ThreadPoolManager.getInstance().scheduleAtFixedRate(new ItemUpdateTask(player.getObjectId()),
                            PeriodicSaveConfig.PLAYER_ITEMS * 1000, PeriodicSaveConfig.PLAYER_ITEMS * 1000));

            SurveyService.getInstance().showAvailable(player);

            if (EventsConfig.ENABLE_EVENT_SERVICE)
                EventService.getInstance().onPlayerLogin(player);

            if (CraftConfig.DELETE_EXCESS_CRAFT_ENABLE)
                RelinquishCraftStatus.removeExcessCraftStatus(player, false);

            PlayerTransferService.getInstance().onEnterWorld(player);
            player.setPartnerId(DAOManager.getDAO(WeddingDAO.class).loadPartnerId(player));
        } else {
            log.info("[DEBUG] enter world" + objectId + ", Player: " + player);
        }
    }

    /**
     * @param client
     * @param player
     */
    // TODO! this method code is really odd [Nemesiss]
    private static void sendItemInfos(AionConnection client, Player player) {
        // Cubesize limit set in inventory.
        int questExpands = player.getQuestExpands();
        int npcExpands = player.getNpcExpands();
        player.setCubeLimit();
        player.setWarehouseLimit();
        // items
        Storage inventory = player.getInventory();
        List<Item> allItems = new ArrayList<Item>();
        if (inventory.getKinah() == 0) {
            inventory.increaseKinah(0); // create an empty object with value 0
        }
        allItems.add(inventory.getKinahItem()); // always included even with 0 count, and first in the packet !
        allItems.addAll(player.getEquipment().getEquippedItems());
        allItems.addAll(inventory.getItems());

        ListSplitter<Item> splitter = new ListSplitter<Item>(allItems, 10);
        while (!splitter.isLast()) {
            client.sendPacket(new SM_INVENTORY_INFO(splitter.isFirst(), splitter.getNext(), npcExpands, questExpands, player));
        }

        client.sendPacket(new SM_INVENTORY_INFO(false, new ArrayList<Item>(0), npcExpands, questExpands, player));
        client.sendPacket(new SM_STATS_INFO(player));
        client.sendPacket(SM_CUBE_UPDATE.stigmaSlots(player.getCommonData().getAdvancedStigmaSlotSize()));
    }

    private static void sendMacroList(AionConnection client, Player player) {
        client.sendPacket(new SM_MACRO_LIST(player, false));
        if (player.getMacroList().getSize() > 7)
            client.sendPacket(new SM_MACRO_LIST(player, true));
    }

    /**
     * @param player
     */
    private static void playerLoggedIn(Player player) {
        log.info("Player logged in: " + player.getName() + " Account: " + player.getClientConnection().getAccount().getName());
        player.getCommonData().setOnline(true);
        DAOManager.getDAO(PlayerDAO.class).onlinePlayer(player, true);
        player.onLoggedIn();
        player.setOnlineTime();
    }

    private static void showPremiumAccountInfo(final AionConnection client, final Account account) {
        final byte membership = account.getAccountMembership().getMembership();
        final byte craftship = account.getAccountMembership().getCraftship();
        final byte apship = account.getAccountMembership().getApship();
        final byte collectionship = account.getAccountMembership().getCollectionship();
        String message = MuiService.getInstance().getMessage("MEMBERSHIP_LOGIN");
        message += "-----------------------------\n";

        if (membership > 0) {
            message += (membership == 1 ? MembershipConfig.TAG_PREMIUM.substring(0, 2) : MembershipConfig.TAG_VIP.substring(0, 2)) + " "
                    + MuiService.getInstance().getMessage("MEMBERSHIP_STATUS", getMembershipTagsRate(account.getAccountMembership().getMembership())) + " "
                    + MuiService.getInstance().getMessage("MEMBERSHIP_EXPIRE", account.getAccountMembership().getExpire(AccountMembershipType.REGULAR));
        }
        if (craftship > 0) {
            message += (craftship == 1 ? MembershipConfig.TAG_CRAFT.substring(0, 2) : MembershipConfig.TAG_CRAFT.substring(0, 2)) + " "
                    + MuiService.getInstance().getMessage("MEMBERSHIP_STATUS", MuiService.getInstance().getNonUTFMessage("MEMBERSHIP_CRAFT")) + " "
                    + MuiService.getInstance().getMessage("MEMBERSHIP_EXPIRE", account.getAccountMembership().getExpire(AccountMembershipType.CRAFT));
        }
        if (apship > 0) {
            message += (apship == 1 ? MembershipConfig.TAG_AP.substring(0, 2) : MembershipConfig.TAG_AP.substring(0, 2)) + " "
                    + MuiService.getInstance().getMessage("MEMBERSHIP_STATUS", MuiService.getInstance().getNonUTFMessage("MEMBERSHIP_AP")) + " "
                    + MuiService.getInstance().getMessage("MEMBERSHIP_EXPIRE", account.getAccountMembership().getExpire(AccountMembershipType.AP));
        }
        if (collectionship > 0) {
            message += (collectionship == 1 ? MembershipConfig.TAG_COLLECTION.substring(0, 2) : MembershipConfig.TAG_COLLECTION.substring(0, 2)) + " "
                    + MuiService.getInstance().getMessage("MEMBERSHIP_STATUS", MuiService.getInstance().getNonUTFMessage("MEMBERSHIP_COLLECTION")) + " "
                    + MuiService.getInstance().getMessage("MEMBERSHIP_EXPIRE", account.getAccountMembership().getExpire(AccountMembershipType.COLLECTION));
        }
        if (membership > 0 || craftship > 0 || apship > 0 || collectionship > 0) {
            //message += "=============================";
            client.sendPacket(new SM_MESSAGE(0, null, message, ChatType.GOLDEN_YELLOW));

            if (CustomConfig.PREMIUM_WORLD_NOTIFY) {
                World.getInstance().doOnAllPlayers(new Visitor<Player>() {
                    @Override
                    public void visit(Player player) {
                        if (player.equals(client.getActivePlayer())) {
                            return;
                        }
                        String membertag = "";
                        if (membership == 1) {
                            membertag += MembershipConfig.TAG_PREMIUM.substring(0, 2) + " ";
                        } else if (membership == 2) {
                            membertag += MembershipConfig.TAG_VIP.substring(0, 2) + " ";
                        }
                        if (craftship > 0) {
                            membertag += MembershipConfig.TAG_CRAFT.substring(0, 2) + " ";
                        }
                        if (apship > 0) {
                            membertag += MembershipConfig.TAG_AP.substring(0, 2) + " ";
                        }
                        if (collectionship > 0) {
                            membertag += MembershipConfig.TAG_COLLECTION.substring(0, 2) + " ";
                        }
                        String message = membertag + "- " + MuiService.getInstance().getMessage("MEMBERSHIP_WORLD_ANNOUNCE", membership > 0 ? getMembershipTagsRate(account.getAccountMembership().getMembership()) : "" + " " + client.getActivePlayer().getName());
                        MuiService.getInstance().sendNonUTFMessage(player, message);
                    }
                });
            }
        }
    }

    private static String getMembershipTagsRate(int membership) {
        return membership == 1 ? MuiService.getInstance().getNonUTFMessage("MEMBERSHIP_PREMIUM") : MuiService.getInstance().getNonUTFMessage("MEMBERSHIP_VIP");
    }

}

class GeneralUpdateTask implements Runnable {

    private static final Logger log = LoggerFactory.getLogger(GeneralUpdateTask.class);
    private final int playerId;

    GeneralUpdateTask(int playerId) {
        this.playerId = playerId;
    }

    @Override
    public void run() {
        Player player = World.getInstance().findPlayer(playerId);
        if (player != null) {
            try {
                DAOManager.getDAO(AbyssRankDAO.class).storeAbyssRank(player);
                DAOManager.getDAO(PlayerSkillListDAO.class).storeSkills(player);
                DAOManager.getDAO(PlayerQuestListDAO.class).store(player);
                DAOManager.getDAO(PlayerDAO.class).storePlayer(player);
                for (House house : player.getHouses())
                    house.save();
            } catch (Exception ex) {
                log.error("Exception during periodic saving of player " + player.getName(), ex);
            }
        }

    }

}

class ItemUpdateTask implements Runnable {

    private static final Logger log = LoggerFactory.getLogger(ItemUpdateTask.class);
    private final int playerId;

    ItemUpdateTask(int playerId) {
        this.playerId = playerId;
    }

    @Override
    public void run() {
        Player player = World.getInstance().findPlayer(playerId);
        if (player != null) {
            try {
                DAOManager.getDAO(InventoryDAO.class).store(player);
                DAOManager.getDAO(ItemStoneListDAO.class).save(player);
            } catch (Exception ex) {
                log.error("Exception during periodic saving of player items " + player.getName(), ex);
            }
        }
    }

}
